package com.xl.bigdata.photo.ai;

/**
 * Copyright (C), 2015-2019, 乐信云科技有限公司
 * FileName: CustomerApp
 * Author:   GaoXL
 * Date:     2020/4/1 14:31
 * Description: 均值哈希算法
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 * GaoXL             2020/4/1 14:31..V1.0.............大数据
 */

import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 均值哈希实现图像指纹比较
 *
 * @author test
 * 背景与原理
 * <p>
 * 前段时间公司项目用到了语音识别,图像识别,视频识别等,其实不能说是识别,应该说是相似度对比吧,毕竟相似度对比还上升不了到识别哈,等以后有了更深的理解再来讨论修改下!这次就当做一个总结吧!
 * <p>
 * 其实它的原理就是一个把需要的特征总结在一个指纹码里面,进行降维成指纹码,假如个指纹码一模一样,那两张图片就想似了.下面有写怎么编译成唯一标识,再用汉明距离计算两个指纹码的相似度.
 * 图像相似度算法:
 * <p>
 * 图片是采用phash算法,一共分为四步吧.
 * <p>
 * 1.将图片缩放到16*16大小,这是我们选择的合适的大小,假如宽高不一样,直接将其压到16*16,去掉细节,只保留宏观;
 * <p>
 * 2.图片一共是16*16的,共256个像素,我们将图片进行灰度化,灰度化就是只有黑白灰三种,从白到黑,一共分了255层;
 * <p>
 * 3.灰度化之后将图片进行DCT转换(离散余弦变化),因为为了识别有的图片旋转,这个DCT转换是将图片进行了一种压缩算法;
 * <p>
 * 4.我们对这个算法进行了优化,因为之前是计算像素的均值,我们为了更准确,我们取RGB,rgb一共分为255个像素,我们将255个像素分为16段,如果像素大于0-16记为0,17到32记为1,直到255,这样就得到255位的二进制,这就是这张图片的指纹码.
 * <p>
 * 得到唯一标识的指纹码之后怎么去计算像素度呢?
 * <p>
 * 通过汉明距离比较两个二进制距离,如果距离小于<10的话,我们就判定两张图片相似.如果两个指纹码(二进制)一模一样,我们就判定两个是一张图片,或者类似;
 * 视频相似度算法:
 * <p>
 * 视频的话我们是通过ffmpeg(ff am pig),它是一个专门处理视频的框架,可以从视频中按针提取图片.然后就按照图片的相似度取对比了...
 * java实现
 */
public final class ImagePHashSimilarity {

    /**
     * 图像指纹的尺寸,将图像resize到指定的尺寸，来计算哈希数组
     */
    private static final int HASH_SIZE = 16;

    /**
     * 保存图像指纹的二值化矩阵
     */
    private final byte[] binaryzationMatrix;

    public ImagePHashSimilarity(byte[] hashValue) {
        if (hashValue.length != HASH_SIZE * HASH_SIZE)
            throw new IllegalArgumentException(String.format("length of hashValue must be %d", HASH_SIZE * HASH_SIZE));
        this.binaryzationMatrix = hashValue;
    }

    public ImagePHashSimilarity(String hashValue) {
        this(toBytes(hashValue));
    }

    public ImagePHashSimilarity(BufferedImage src) {
//        this(initChangeMap());

        this(hashValue(src));
    }

    private static byte[] hashValue(BufferedImage src) {
        initChangeMap();
        BufferedImage hashImage = resize(src, HASH_SIZE, HASH_SIZE);
        byte[] matrixGray = (byte[]) toGray(hashImage).getData().getDataElements(0, 0, HASH_SIZE, HASH_SIZE, null);

        return binaryzation(matrixGray);
    }

    /**
     * 从压缩格式指纹创建{@link ImagePHashSimilarity}对象
     *
     * @param compactValue
     * @return
     */
    public static ImagePHashSimilarity createFromCompact(byte[] compactValue) {
        return new ImagePHashSimilarity(uncompact(compactValue));
    }

    public static boolean validHashValue(byte[] hashValue) {
        if (hashValue.length != HASH_SIZE)
            return false;
        for (byte b : hashValue) {
            if (0 != b && 1 != b) return false;
        }
        return true;
    }

    public static boolean validHashValue(String hashValue) {
        if (hashValue.length() != HASH_SIZE)
            return false;
        for (int i = 0; i < hashValue.length(); ++i) {
            if ('0' != hashValue.charAt(i) && '1' != hashValue.charAt(i)) return false;
        }
        return true;
    }

    public byte[] compact() {
        return compact(binaryzationMatrix);
    }

    /**
     * 指纹数据按位压缩
     *
     * @param hashValue
     * @return
     */
    private static byte[] compact(byte[] hashValue) {
        byte[] result = new byte[(hashValue.length + 7) >> 3];
        byte b = 0;
        for (int i = 0; i < hashValue.length; ++i) {
            if (0 == (i & 7)) {
                b = 0;
            }
            if (1 == hashValue[i]) {
                b |= 1 << (i & 7);
            } else if (hashValue[i] != 0)
                throw new IllegalArgumentException("invalid hashValue,every element must be 0 or 1");
            if (7 == (i & 7) || i == hashValue.length - 1) {
                result[i >> 3] = b;
            }
        }
        return result;
    }

    /**
     * 压缩格式的指纹解压缩
     *
     * @param compactValue
     * @return
     */
    private static byte[] uncompact(byte[] compactValue) {
        byte[] result = new byte[compactValue.length << 3];
        for (int i = 0; i < result.length; ++i) {
            if ((compactValue[i >> 3] & (1 << (i & 7))) == 0)
                result[i] = 0;
            else
                result[i] = 1;
        }
        return result;
    }

    /**
     * 字符串类型的指纹数据转为字节数组
     *
     * @param hashValue
     * @return
     */
    private static byte[] toBytes(String hashValue) {
        hashValue = hashValue.replaceAll("\\s", "");
        byte[] result = new byte[hashValue.length()];
        for (int i = 0; i < result.length; ++i) {
            char c = hashValue.charAt(i);
            if ('0' == c)
                result[i] = 0;
            else if ('1' == c)
                result[i] = 1;
            else
                throw new IllegalArgumentException("invalid hashValue String");
        }
        return result;
    }

    /**
     * 缩放图像到指定尺寸
     *
     * @param src
     * @param width
     * @param height
     * @return
     */
    private static BufferedImage resize(Image src, int width, int height) {
        BufferedImage result = new BufferedImage(width, height,
                BufferedImage.TYPE_3BYTE_BGR);
        Graphics g = result.getGraphics();
        try {
            if (src != null) {
                g.drawImage(src.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            g.dispose();
        }
        return result;
    }

    /**
     * 计算均值
     *
     * @param src
     * @return
     */
    private static int mean(byte[] src) {
        long sum = 0;
        // 将数组元素转为无符号整数
        for (byte b : src) sum += (long) b & 0xff;
        return (int) (Math.round((float) sum / src.length));
    }

    /**
     * 二值化处理
     *
     * @param src
     * @return
     */
    private static byte[] binaryzation(byte[] src) {
        byte[] dst = src.clone();
        int mean = mean(src);
        for (int i = 0; i < dst.length; ++i) {
            // 将数组元素转为无符号整数再比较
            dst[i] = (byte) (((int) dst[i] & 0xff) >= mean ? 1 : 0);
        }
        return dst;

    }

    /**
     * 转灰度图像
     *
     * @param src
     * @return
     */
    private static BufferedImage toGray(BufferedImage src) {
        if (src.getType() == BufferedImage.TYPE_BYTE_GRAY) {
            return src;
        } else {
            // 图像转灰
            BufferedImage grayImage = new BufferedImage(src.getWidth(), src.getHeight(),
                    BufferedImage.TYPE_BYTE_GRAY);
            new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null).filter(src, grayImage);
            return grayImage;
        }
    }

    @Override
    public String toString() {


        return toString26();//toString(true);
    }

    /**
     * @param multiLine 是否分行
     * @return
     */
    public String toString(boolean multiLine) {
        StringBuffer buffer = new StringBuffer();
        int count = 0;
        for (byte b : this.binaryzationMatrix) {
            buffer.append(0 == b ? '0' : '1');
//            if(multiLine&&++count%HASH_SIZE==0)
//                buffer.append('\n');
        }

        return buffer.toString();
    }

    /**
     * @param
     * @return
     */
    public String toString26() {
        StringBuffer buffer = new StringBuffer();
        StringBuffer buffer1 = new StringBuffer();
        int count = 0;
        for (byte b : this.binaryzationMatrix) {
            buffer.append(0 == b ? '0' : '1');
            if (++count % 4 == 0) {
                String re = changeMap.get(buffer.toString());
                if (re == null) {
                    System.out.println(buffer.toString());
                }
                buffer1.append(re);
                buffer = new StringBuffer();
            }
        }
        return buffer1.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ImagePHashSimilarity) {
            return Arrays.equals(this.binaryzationMatrix, ((ImagePHashSimilarity) obj).binaryzationMatrix);
        } else
            return super.equals(obj);
    }

    /**
     * 与指定的压缩格式指纹比较相似度
     *
     * @param compactValue
     * @return
     * @see #compare(ImagePHashSimilarity)
     */
    public float compareCompact(byte[] compactValue) {
        return compare(createFromCompact(compactValue));
    }

    /**
     * @param hashValue
     * @return
     * @see #compare(ImagePHashSimilarity)
     */
    public float compare(String hashValue) {
        return compare(new ImagePHashSimilarity(hashValue));
    }

    /**
     * 与指定的指纹比较相似度
     *
     * @param hashValue
     * @return
     * @see #compare(ImagePHashSimilarity)
     */
    public float compare(byte[] hashValue) {
        return compare(new ImagePHashSimilarity(hashValue));
    }

    /**
     * 与指定图像比较相似度
     *
     * @param image2
     * @return
     * @see #compare(ImagePHashSimilarity)
     */
    public float compare(BufferedImage image2) {
        return compare(new ImagePHashSimilarity(image2));
    }

    /**
     * 比较指纹相似度
     *
     * @param src
     * @return
     * @see #compare(byte[], byte[])
     */
    public float compare(ImagePHashSimilarity src) {
        if (src.binaryzationMatrix.length != this.binaryzationMatrix.length)
            throw new IllegalArgumentException("length of hashValue is mismatch");
        return compare(binaryzationMatrix, src.binaryzationMatrix);
    }

    /**
     * 判断两个数组相似度，数组长度必须一致否则抛出异常
     *
     * @param f1
     * @param f2
     * @return 返回相似度(0.0 ~ 1.0)
     */
    private static float compare(byte[] f1, byte[] f2) {
        if (f1.length != f2.length)
            throw new IllegalArgumentException("mismatch ImagePHashSimilarity length");
        int sameCount = 0;
        for (int i = 0; i < f1.length; ++i) {
            if (f1[i] == f2[i]) ++sameCount;
        }
        return (float) sameCount / f1.length;
    }

    public static float compareCompact(byte[] f1, byte[] f2) {
        return compare(uncompact(f1), uncompact(f2));
    }

    public static float compare(BufferedImage image1, BufferedImage image2) {
        return new ImagePHashSimilarity(image1).compare(new ImagePHashSimilarity(image2));
    }

    public static Map<String, String> changeMap = new HashMap<>();

    public static void initChangeMap() {

        changeMap.put("1111", "a");
        changeMap.put("1110", "b");
        changeMap.put("1101", "c");
        changeMap.put("1011", "v");
        changeMap.put("0111", "d");
        changeMap.put("1100", "e");
        changeMap.put("1010", "f");
        changeMap.put("1001", "g");
        changeMap.put("0110", "h");
        changeMap.put("0101", "i");
        changeMap.put("0011", "j");
        changeMap.put("1000", "k");
        changeMap.put("0100", "l");
        changeMap.put("0010", "m");
        changeMap.put("0001", "n");
        changeMap.put("0000", "o");

    }

    public static String changeString() {


        return "";
    }
}
